# vPresent is an immersive presentation creation and display application.
# Copyright (C) 2007 by Infiscape Corporation
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import mimetypes
import os, os.path
import traceback
import zipfile
import datetime, time
from PyQt4 import QtCore

import opendocument as od
import xml
import gui.util.doctype as doctype
import gui.util.qthelpers as qthelpers


class Reader(xml.Reader):
   '''
   Loads an OpenDocument document archive and creates a document from it
   using the given builder.
   '''

   def __init__(self, fileName, builder):
      xml.Reader.__init__(self, builder)
      self.fileName = fileName

   def construct(self, workDir):
      if not os.path.exists(workDir):
         raise ValueError("%s does not exist" % workDir)

      cwd = os.getcwd()
      os.chdir(workDir)
      archive = zipfile.ZipFile(self.fileName, 'r')
      result = archive.testzip()

      for n in archive.namelist():
         # XXX: Is this the best way to determine when an archive entry is a
         # directory?
         if n.endswith('/') or n.endswith('\\'):
            if not os.path.exists(n):
               os.makedirs(n)
         else:
            dir = os.path.dirname(n)

            if dir:
               if not os.path.exists(dir):
                  os.makedirs(dir)
               elif not os.path.isdir(dir):
                  print "WARNING: %s is expected to be a directory!" % dir

            info = archive.getinfo(n)

            file = open(n, 'w+b')
            file.write(archive.read(n))
            file.close()

            # Preserve the modification time. Set the access time to be now.
            mtime = datetime.datetime(*info.date_time)
            os.utime(n, (time.mktime(time.localtime()),
                         time.mktime(mtime.timetuple())))

      archive.close()

      base_err_msg_string = '%s from ' + self.fileName

      manifest_file_name = os.path.join('META-INF', 'manifest.xml')
      if os.path.exists(manifest_file_name):
         err_msg_string = base_err_msg_string % manifest_file_name
         manifest_doc = qthelpers.loadXMLDocument(manifest_file_name,
                                                  QtCore.QIODevice.ReadWrite,
                                                  err_msg_string)
      else:
         raise Exception("%s lacks manifest" % self.fileName)

      self.__validateStructure(manifest_doc)

      content_file_name = 'content.xml'
      if os.path.exists(content_file_name):
         err_msg_string = base_err_msg_string % content_file_name
         content_doc = qthelpers.loadXMLDocument(content_file_name,
                                                 QtCore.QIODevice.ReadWrite,
                                                 err_msg_string)
      else:
         raise Exception("%s lacks %s" % (self.fileName, content_file_name))

      meta_doc = None
      meta_file_name = 'meta.xml'
      if os.path.exists(meta_file_name):
         try:
            err_msg_string = base_err_msg_string % meta_file_name
            meta_doc = qthelpers.loadXMLDocument(meta_file_name,
                                                 QtCore.QIODevice.ReadWrite,
                                                 err_msg_string)
         except Exception, ex:
            traceback.print_exc()

      settings_doc = None
      settings_file_name = 'settings.xml'
      if os.path.exists(settings_file_name):
         try:
            err_msg_string = base_err_msg_string % settings_file_name
            settings_doc = \
               qthelpers.loadXMLDocument(settings_file_name,
                                         QtCore.QIODevice.ReadWrite,
                                         err_msg_string)
         except Exception, ex:
            traceback.print_exc()

      styles_doc = None
      styles_file_name = 'styles.xml'
      if os.path.exists(styles_file_name):
         try:
            err_msg_string = base_err_msg_string % styles_file_name
            styles_doc = qthelpers.loadXMLDocument(styles_file_name,
                                                   QtCore.QIODevice.ReadWrite,
                                                   err_msg_string)
         except Exception, ex:
            traceback.print_exc()

      os.chdir(cwd)

      content_root = content_doc.documentElement()

      styles_root   = None
      meta_root     = None
      settings_root = None

      if meta_doc is not None:
         meta_root = meta_doc.documentElement()

      if styles_doc is not None:
         styles_root = styles_doc.documentElement()

      if settings_doc is not None:
         settings_root = settings_doc.documentElement()

      xml.Reader.construct(self, content_root, meta_root, styles_root,
                              settings_root)

   def __validateStructure(self, manifestDoc):
      manifest_root = manifestDoc.documentElement()
      children = manifest_root.childNodes()

      # Ensure that all the directories referenced in the manifest exist.
      # XXX: This is a hack to deal with the writer not being able to preserve
      # empty directories due to limitations of zipfile.ZipFile. See
      # archive.Writer.write() for more information.
      for i in xrange(children.count()):
         child = children.item(i)
         if child.nodeType() == 'file-entry':
            path = child.getAttribute('full-path')
            if path != '/' and path.endswith('/') and not os.path.exists(path):
               os.makedirs(path)

class Writer(xml.Writer):
   def __init__(self, fileName):
      self.fileName = fileName

   def write(self, document, workDir):
      if not os.path.exists(workDir):
         raise ValueError("%s does not exist" % workDir)

      cwd = os.getcwd()
      os.chdir(workDir)

      content_doc  = xml.createXMLDocument()
      content_root = xml.createContentRoot(content_doc)
      content_doc.appendChild(content_root)

      meta_doc  = xml.createXMLDocument()
      meta_root = xml.createMetaRoot(meta_doc, document.getContentType())
      meta_doc.appendChild(meta_root)

      settings_doc  = xml.createXMLDocument()
      settings_root = xml.createSettingsRoot(settings_doc,
                                                document.getContentType())
      settings_doc.appendChild(settings_root)

      styles_doc  = xml.createXMLDocument()
      styles_root = xml.createStylesRoot(styles_doc)
      styles_doc.appendChild(styles_root)

      xml.Writer.write(self, document, content_doc, content_root, meta_doc,
                          meta_root, settings_doc, settings_root, styles_doc,
                          styles_root)

      self._cleanChildren(content_doc, content_root)
      self._cleanChildren(meta_doc, meta_root)
      self._cleanChildren(settings_doc, settings_root)
      self._cleanChildren(styles_doc, styles_root)

      content_doc_str = content_doc.toString(3)
      f = open('content.xml', 'w')
      f.write(content_doc_str)
      f.close()

      meta_doc_str = meta_doc.toString(3)
      f = open('meta.xml', 'w')
      f.write(meta_doc_str)
      f.close()

      settings_doc_str = settings_doc.toString(3)
      f = open('settings.xml', 'w')
      f.write(settings_doc_str)
      f.close()

      styles_doc_str = styles_doc.toString(3)
      f = open('styles.xml', 'w')
      f.write(styles_doc_str)
      f.close()

      if not os.path.exists('mimetype'):
         f = open('mimetype', 'w')
         f.write(document.getContentType())
         f.close()

      manifest_doc = xml.createXMLDocument()
      manifest_root = xml.createManifestRoot(manifest_doc,
                                                document.getMimeType())
      manifest_doc.appendChild(manifest_root)

      manifest_ns = xml.CORE_NAMESPACES['manifest']

      for dirpath, dirs, files in os.walk('.', False):
         if dirpath == './META-INF':
            continue

         for d in dirs:
            if d == 'META-INF':
               continue

            entry = manifest_doc.createElementNS(manifest_ns,
                                                  'manifest:file-entry')

            if d == 'Configurations2':
               entry.setAttributeNS(manifest_ns, 'manifest:media-type',
                                    'application/vnd.sun.xml.ui.configuration')
            else:
               entry.setAttributeNS(manifest_ns, 'manifest:media-type', '')

            entry.setAttributeNS(
               manifest_ns, 'manifest:full-path',
               os.path.normpath(os.path.join(dirpath, d)) + '/'
            )
            manifest_root.appendChild(entry)

         for f in files:
            entry = manifest_doc.createElementNS(manifest_ns,
                                                 'manifest:file-entry')
            ext = os.path.splitext(f)[1]

            try:
               mime_type = mimetypes.types_map[ext]
            except KeyError:
               mime_type = ''

            entry.setAttributeNS(manifest_ns, 'manifest:media-type',
                                 mime_type)
            entry.setAttributeNS(manifest_ns, 'manifest:full-path',
                                 os.path.normpath(os.path.join(dirpath, f)))
            manifest_root.appendChild(entry)

      if not os.path.exists('META-INF'):
         os.mkdir('META-INF')

      self._cleanChildren(manifest_doc, manifest_root)

      manifest_doc_str = manifest_doc.toString(3)
      f = open(os.path.join('META-INF', 'manifest.xml'), 'w')
      f.write(manifest_doc_str)
      f.close()

      try:
         archive = zipfile.ZipFile(self.fileName, 'w', zipfile.ZIP_DEFLATED)
      except RuntimeError:
         archive = zipfile.ZipFile(self.fileName, 'w', zipfile.ZIP_STORED)

      # XXX: This does not preserve empty directories.
      for dirpath, dirs, files in os.walk('.', True):
         for f in files:
            archive.write(os.path.normpath(os.path.join(dirpath, f)))

      archive.close()

      os.chdir(cwd)

class ReaderWriter:
   sFileType = None

   def getFileTypesByExt(self):
      return self.sFileType.getExtensions()

   def getDocumentType():
      return 'application/vnd.oasis.opendocument.presentation'

   getDocumentType = staticmethod(getDocumentType)

   def init(env):
      dt = \
         doctype.DocType('Presentation',
                         {'odp' : ReaderWriter.getDocumentType()},
                         Reader, Writer)
      env.registerDocType(dt)
      ReaderWriter.sFileType = dt

   init = staticmethod(init)
